#Binary parser Illusion_xxlate

import os, sys, re
from PIL import Image
from Sanae3D.lib.ModelObject import Model3D
from Sanae3D.lib.StructReader import StructReader
from Sanae3D.lib.StructWriter import StructWriter
                
class Illusion_xx(Model3D):
    '''Replace 'Illusion_xx' with a suitable name of your choice. Try to follow
    the standard: GameName_FormatExt'''
    
    def __init__(self, dirPath, obj3D=None, outFile=None, inFile=None):
        
        super(Illusion_xx, self).__init__("XX")
        self.obj3D = obj3D
        self.inFile = inFile
        self.outFile = outFile
        self.frameList = []
        self.meshList = []
        self.xxFormat = -1
	self.dirPath = dirPath
        
    def decrypt_string(self, string):
        '''Return the decrypted string. XOR each character in the string by
        FF to get the actual character. Strings are null-terminated.'''
        
        inverted = ""
        for i in xrange(len(string)-1):
            inverted += chr(ord(string[i]) ^ 0xFF)
        return inverted
    
    def read_name(self):
        
        length = self.inFile.read_long()
        return self.decrypt_string(self.inFile.read_string(length))
        
    def get_format(self):
        
        xxFormat = 0
        fmt = self.inFile.read_byte()
        xxInt = self.inFile.read_byte(4)
        if (fmt >= 0x01 and fmt <= 0x06 and xxInt[0] == 0):
            xxFormat = fmt
        #elif xxInt in (0x3F8F5C29, 0x3F90A3D7, 0x3F91EB85, 0x3F933333, 
                       #0x3F947AE1, 0x3F95C28F, 0x3F970A3D, 0x3F99999A,
                       #0x3FA66666, 0x3FB33333):
	elif xxInt[2] == 0x3F:
            xxFormat = -1
        return xxFormat
        
    def parse_textures(self):
        
        numTex = self.inFile.read_long()
	print numTex
        for i in range(numTex):
            texName = self.read_name()
	    texPath = os.path.join(self.dirPath, texName)
	    
	    print i, texName
	    texUnk1 = self.inFile.read_byte(4)
            width, height = self.inFile.read_long(2)
            texUnk2 = self.inFile.read_byte(20)
            checksum = self.inFile.read_byte(1)
            imageDataSize = self.inFile.read_long()
            
            ext = os.path.splitext(texName)[1]
            outPic = open(texPath, 'wb')
            outPic.write(self.inFile.read(imageDataSize))
            
            if ext.upper() == ".BMP":
                outPic.seek(0)
                outPic.write("BM")
            outPic.close()
	    
	    self.create_texture(texName)
	    self.add_texture_checksum(texName, checksum)
	    self.add_texture_unknown(texName, texUnk1)
	    self.add_texture_unknown(texName, texUnk2)
        return numTex

    def parse_material_tex(self):
        
        matTextures = []
	
        for j in range(4):
            matTexName = self.read_name()
            MatTexUnknown = self.inFile.read_byte(16)
            matTextures.append(matTexName)
        return matTextures
        
    def parse_materials(self):
        
        matSectUnknown = self.inFile.read_byte(4)
	numMaterial = self.inFile.read_long(1)
        for i in range(numMaterial):
            matName = self.read_name()
            
            diffuse = self.inFile.read_float(4)
            ambient = self.inFile.read_float(4)
            specular = self.inFile.read_float(4)
            emissive = self.inFile.read_float(4)
            power = self.inFile.read_float()
            
            matTextures = self.parse_material_tex()
	    
            if self.xxFormat < 0:
                matUnk1 = self.inFile.read_byte(4)
            else:
                matUnk1 = self.inFile.read_byte(88)
            
            self.create_material(i)
            self.add_material_name(i, matName)
            self.add_texture_name(i, matTextures[0])
            self.add_ambient(i, ambient)
            self.add_diffuse(i, diffuse)
            self.add_specular(i, specular)
            self.add_emissive(i, emissive)
            self.add_power(i, power)
	    self.add_material_unknown(i, matSectUnknown)
	    self.add_material_unknown(i, matUnk1)
        return numMaterial
        
    def parse_faces(self, objName, matNum):
        
        numFaces = self.inFile.read_long() / 3
        for i in range(numFaces):
            v1, v2, v3 = self.inFile.read_short(3)
            
            self.create_face(objName, i)
            self.add_face_verts(objName, i, [v1, v2, v3])
            self.add_face_material(objName, i, matNum)
        return numFaces
        
    def parse_vertices(self, objName):
        
        numVerts = self.inFile.read_long()
        for i in range(numVerts):
            #unknown index
            if self.xxFormat >= 4:
                index = self.inFile.read_short()
            else:
                index = self.inFile.read_long()
                
            vx, vy, vz, w1, w2, w3 = self.inFile.read_float(6)
            b1, b2, b3, b4 = self.inFile.read_byte(4)
            nx, ny, nz, tu, tv = self.inFile.read_float(5)
            
            if self.xxFormat >= 4:
                unk = self.inFile.read_byte(20)
            else:
                unk = 0
            
            self.create_vertex(objName, i)
            self.add_coords(objName, i, [vx, vy, vz])
            self.add_vert_uv(objName, i, [tu, tv])
            self.add_vert_normals(objName, i, [nx, ny, nz])
	    self.add_vert_bones(objName, i, [b1, b2, b3, b4])
	    self.add_vert_weights(objName, i, [w1, w2, w3])
        return numVerts
    
    def parse_dupe_vertices(self):
        
        numVertDupe = self.inFile.read_short()
        self.inFile.read_long(2)
        
        for i in range(numVertDupe):
            if self.xxFormat >= 4:
                index = self.inFile.read_short()
            else:
                index = self.inFile.read_long()
                
            vx, vy, vz = self.inFile.read_float(3)
            weights = self.inFile.read_float(3)
            boneIndices = self.inFile.read_byte(4)
            nx, ny, nz = self.inFile.read_float(3)
            tu, tv = self.inFile.read_float(2)
            
            if self.xxFormat >= 4:
                unk = self.inFile.read_byte(20)
            else:
                unk = 0
                
            vx = float(vx) * -1
                
        return numVertDupe
        
    def parse_bones(self, objName, frameIndex):
        
        numBones = self.inFile.read_long()
        for i in xrange(numBones):
            
            length = self.inFile.read_long()
            boneName = self.decrypt_string(self.inFile.read_string(length))
            boneIndex = self.inFile.read_long()
            boneTransform = self.inFile.read_float(16)
	    
	    boneNum = self.bone_count()
	    self.create_bone(boneNum)
	    self.add_bone_name(boneNum, boneName)
	    self.add_bone_transform(boneNum, boneTransform)
	    self.add_mesh_bone(objName, boneNum)
            
        return numBones
    
    def parse_mesh(self, numMesh, name, frameIndex):
        
        if numMesh > 0:
	    #if self.xxFormat >= 6: not for wakeari?
            if self.xxFormat > 6:
                charCount = self.inFile.read_long()
                meshName = self.decrypt_string(self.inFile.read_string(charCount)[0])
            else:
                meshName = name
            
            meshType = self.inFile.read_byte()
            for i in xrange(numMesh):
                
                if numMesh == 1:
                    objName = meshName
                else:
                    objName = "%s[%d]" %(meshName, i)
                self.create_object(objName)
                
                newIndex = self.frame_count()
		self.add_frame_mesh(frameIndex, objName)
                
                meshUnknown = self.inFile.read_float(4)   #unknown
		self.add_mesh_unknown(objName, meshUnknown)
                matNum = self.inFile.read_long()  #material index
                
                numFaces = self.parse_faces(objName, matNum)
                numVerts = self.parse_vertices(objName)
                unknownA = self.inFile.read_byte(numVerts * 8 * meshType)
		self.add_mesh_unknown(objName, unknownA)
                if self.xxFormat >= 2:
                    unknownB = self.inFile.read_byte(100)
		    self.add_mesh_unknown(objName, unknownB)
                else:
                    unknownB = self.inFile.read_byte(0)
                    
                if self.xxFormat >= 3:
                    unknownC = self.inFile.read_byte(64)
		    self.add_mesh_unknown(objName, unknownC)
                else:
                    unknownC = self.inFile.read_byte(0)
                    
                if self.xxFormat >= 5:
                    unknownD = self.inFile.read_byte(20)
		    self.add_mesh_unknown(objName, unknownD)
                else:
                    unknownD = self.inFile.read_byte(0)
                    
                if self.xxFormat >= 6:
                    unknownE = self.inFile.read_byte(28)
		    self.add_mesh_unknown(objName, unknownE)
                else:
                    unknownE = self.inFile.read_byte(0)
            
            self.parse_dupe_vertices()               
            numBones = self.parse_bones(objName, frameIndex)
        
    def parse_frames(self, parent=-1):
        '''Parse frames. Frame count starts at 0, and increments with each
        successful iteration of this method. The parent is the index of the
        parent frame, which is typically the previous frame index'''
        
        charCount = self.inFile.read_long()
        name = self.decrypt_string(self.inFile.read_string(charCount))
        frameName = name
        numChildren = self.inFile.read_long()  #numChildFrames
        frameTransform = self.inFile.read_float(16)    #transform matrix
        frameUnknownA = self.inFile.read_float(4)      #unknown
        
        numMesh = self.inFile.read_long()
        bbMin = self.inFile.read_float(3)   #min bounds
        bbMax =  self.inFile.read_float(3)   #max bounds
        frameUnknownB = self.inFile.read_float(4)   #unknown
	if self.xxFormat == 6:
	    unk5 = self.inFile.read_long()
        
        frameNum = self.frame_count()
	self.frameList.append(frameName)
        self.create_frame(frameNum)
        self.add_frame_name(frameNum, name)
        self.add_frame_transform(frameNum, frameTransform)
	self.add_frame_unknown(frameNum, frameUnknownA)
	self.add_frame_unknown(frameNum, frameUnknownB)
	self.add_frame_bbox(frameNum, bbMin)
	self.add_frame_bbox(frameNum, bbMax)

        if parent != -1:
            self.add_frame_parent(frameNum, parent)
            self.add_frame_child(parent, frameName)
        self.parse_mesh(numMesh, name, frameNum)
        for j in xrange(numChildren):
            self.parse_frames(frameNum)
        
    def parse_file(self):
        '''Main parser method. Can be replaced'''
        self.xxFormat = self.get_format()
        #go back to beginning
        self.inFile.seek(0)
        #objectSection [0] unknown
        if self.xxFormat > 0:
            self.inFile.read(26)
        else:
            self.inFile.read(21)
        self.parse_frames()
        
        #for i in range(self.frame_count()):
            #print self.get_frame_name(i), self.get_frame_children(i)
        numMat = self.parse_materials()
        numTex = self.parse_textures()
        
        #unknown object end
        if self.xxFormat < 2:
            unk = 0
        else:
            unk = self.inFile.read_byte(10)
            
class IllusionXX_Writer(Model3D):
    
    def __init__(self, obj3D=None, outFile=None, inFile=None):
        
        super(IllusionXX_Writer, self).__init__("XX")
        self.obj3D = obj3D
        self.inFile = inFile
        self.outFile = outFile
        self.frameList = []
        self.meshList = []
        self.frames = {}
        self.xxFormat = -1
    
    def encrypt_string(self, string):
        '''Return the decrypted string. XOR each character in the string by
        FF to get the actual character. Strings are null-terminated.'''
        
        inverted = ""
        for i in xrange(len(string)):
            inverted = ''.join([inverted, chr(ord(string[i]) ^ 0xFF)])
        return inverted
    
    def negative_floats(self, *floats):
	
	new_floats = []
	for num in floats:
	    if num >= 0:
		num *= -1.0
	    new_floats.append(num)
	return new_floats
    
    def write_name(self, name):
        
        if name:
            name = ''.join([name, "\x00"])
            self.outFile.write_long(len(name))
            enc_name = self.encrypt_string(name)
            self.outFile.write_string(enc_name)
        else:
            self.outFile.write_long(0)
	    
    def calculate_checksum(self, image, imageType):
	
	pass
        
    def write_faces(self, meshName):
        
        numFaces = self.obj3D.face_count(meshName)
        numIndices = numFaces * 3
        self.outFile.write_long(numIndices)
	
	faces = []
	append = faces.append
        for i in xrange(numFaces):
            v1, v2, v3 = self.obj3D.get_face_verts(meshName, i)
	    append(v1)
	    append(v2)
	    append(v3)
	self.outFile.write_ushort(*faces)
        return numIndices
    
    def write_dupe_vertices(self, meshname):
        
        self.outFile.write_short(0) #no dupes
        self.outFile.write_string("\x12\x01\x00\x00\x20\x00\x00\x00")
        
    def write_vertices(self, meshName):
        
        vertices = self.obj3D.get_vertices(meshName)
        numVerts = len(vertices)
        self.outFile.write_long(numVerts)
	
        for i in xrange(len(vertices)):
	    
            vx, vy, vz = self.obj3D.get_coords(meshName, i)
	    w1, w2, w3 = self.obj3D.get_vert_weights(meshName, i)
	    b1, b2, b3, b4 = self.obj3D.get_vert_bones(meshName, i)
            nx, ny, nz = self.obj3D.get_vert_normals(meshName, i)
            tu, tv = self.obj3D.get_vert_uv(meshName, i)
            
            if self.xxFormat >= 4:
                self.outFile.write_short(i)
            else:
                self.outFile.write_long(i)
            
            self.outFile.write_float(vx, vy, vz, w1, w2, w3)
            self.outFile.write_byte(b1, b2, b3, b4)
            self.outFile.write_float(nx, ny, nz, tu, tv)
        return numVerts
            
    def write_bones(self, meshName):
        
	bones = self.obj3D.get_mesh_bones(meshName)
	boneCount = len(bones)
	
	self.outFile.write_long(boneCount)
	if boneCount:
	    for i in xrange(boneCount):
		
		boneIndex = bones[i]
		boneName = self.obj3D.get_bone_name(boneIndex)
		boneNum = i
		boneTransform = self.obj3D.get_bone_transform(boneIndex)
		
		self.write_name(boneName)
		self.outFile.write_long(boneNum)
		self.outFile.write_float(*boneTransform)
    
    def write_header(self, version):
        
        self.outFile.write_byte(version)
        self.outFile.write_long(0)
        #self.outFile.write_string("\x00\x9A\x99\xC1")
	self.outFile.write_string("\xC3\xF5\xC0\x40")
        self.outFile.write_null(17)
        
    def write_mesh(self, meshName, meshType):
        
	meshUnknown = self.obj3D.get_mesh_unknown(meshName, 0)
	if meshUnknown:
	    self.outFile.write_float(*meshUnknown)
	else:
	    string = "\x02\x64\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
	    self.outFile.write_string(string)
        
        #get material used
        matNum = self.obj3D.get_face_mat(meshName, 0)
        self.outFile.write_long(matNum)
        numFaces = self.write_faces(meshName)
        numVerts = self.write_vertices(meshName)
        
        #unknownA
	unknownA = self.obj3D.get_mesh_unknown(meshName, 1)
	if unknownA:
	    self.obj3D.write_byte(*unknownA)
	else:
	    self.outFile.write_null(numVerts * 8 * meshType)
        
        #unknown B
        if self.xxFormat >= 2:
	    unknownB = self.obj3D.get_mesh_unknown(meshName, 2)
	    if unknownB:
		self.outFile.write_byte(*unknownB)
	    else:
		self.outFile.write_null(100)
                  
        #unknown C
        if self.xxFormat >= 3:
	    unknownC = self.obj3D.get_mesh_unknown(meshName, 3)
	    if unknownC:
		self.outFile.write_byte(*unknownC)
	    else:
		self.outFile.write_null(64)
             
        #unknown D
        if self.xxFormat >= 5:
	    unknownD = self.obj3D.get_mesh_unknown(meshName, 4)
	    if unknownD:
		self.outFile.write_byte(*unknownD)
	    else:
		self.outFile.write_null(20)
                 
        #unknown E
        if self.xxFormat >= 6:
	    unknownE = self.obj3D.get_mesh_unknown(meshName, 5)
	    if unknownE:
		self.outFile.write_byte(*unknownE)
	    else:
		self.outFile.write_null(28)
        
    def write_materials(self):
        
        materials = self.obj3D.get_materials()
        matCount = len(materials)
	matSectUnk = self.obj3D.get_material_unknown(0, 0)
	if matSectUnk:
	    self.outFile.write_byte(*matSectUnk)
	else:
	    self.outFile.write_string("\x9A\x19\x07\x44")
        self.outFile.write_long(matCount)
        for i in xrange(matCount):
            
            matName = self.obj3D.get_material_name(i)
            ambient = self.obj3D.get_ambient(i)
            diffuse = self.obj3D.get_diffuse(i)
            specular = self.obj3D.get_specular(i)
            emissive = self.obj3D.get_emissive(i)
            power = self.obj3D.get_power(i)
	    
	    ambient = self.negative_floats(*ambient)
	    diffuse = self.negative_floats(*diffuse)
	    specular = self.negative_floats(*specular)
	    emissive = self.negative_floats(*emissive)
	    #if power >= 0:
		#power *= -1.0
            
            self.write_name(matName.lower())
            
            self.outFile.write_float(*diffuse)
            self.outFile.write_float(*ambient)
            self.outFile.write_float(*specular)
            self.outFile.write_float(*emissive)
            self.outFile.write_float(power)
            
            self.write_material_tex(i)
	    
	    matUnk1 = self.obj3D.get_material_unknown(i, 1)
	    if matUnk1 and len(matUnk1) == 88:
		self.outFile.write_byte(*matUnk1)
	    else:
		self.outFile.write_byte(0, 1, 2, 3)
		self.outFile.write_null(84)
            
    def write_material_tex(self, matNum):
        
        texName = self.obj3D.get_material_texture(matNum)
        self.write_name(texName.lower())
        self.outFile.write_null(16)

        for i in xrange(3):
            self.write_name("")
            self.outFile.write_null(16)
        
    def write_textures(self):
        
        textures = self.obj3D.get_textures()
	
        numTex = len(textures)
        texFound = 0
        
        texPos = self.outFile.tell()
        self.outFile.write_long(numTex)
        for i in xrange(numTex):
            texName = textures[i]
            checksum = self.obj3D.get_texture_checksum(texName)
            try:
                image = open(texName, 'rb')
                image.seek(0, 2)
                size = image.tell()
                image.seek(0)
		
                im = Image.open(image)
                width, height = im.size
                imgFormat = im.format
                
                self.write_name(texName.lower())
		unk1 = self.obj3D.get_texture_unknown(texName, 0)
		unk2 = self.obj3D.get_texture_unknown(texName, 1)
		
		if unk1:
		    self.outFile.write_byte(*unk1)
		else:
		    self.outFile.write_null(4)
                self.outFile.write_long(width, height)
		if unk2:
		    self.outFile.write_byte(*unk2)            
		else:
		    self.outFile.write_long(1, 1, 0x14, 3, 3)
                self.outFile.write_byte(checksum) #TO-DO checksum
		
                self.outFile.write_long(size)
                
                if imgFormat.upper() == "BMP":
                    self.outFile.write_null(2)
                    image.seek(2)
                    self.outFile.write(image.read())
                else:
                    image.seek(0)
                    self.outFile.write(image.read())
                
                texFound += 1
                image.close()
	    except:
                continue
            
        curPos = self.outFile.tell()
        self.outFile.seek(texPos)
        self.outFile.write_long(texFound)
        self.outFile.seek(curPos)
        print "found %d of %d textures successfully" %(texFound, numTex)
        
    def write_frame(self, root):
        
        frameIndex = self.obj3D.get_frame_index(root)
        subFrames = self.obj3D.get_frame_children(frameIndex)
        meshes = self.obj3D.get_frame_mesh(frameIndex)
        transform = self.obj3D.get_frame_transform(frameIndex)
	numChildren = len(subFrames)
        numMesh = len(meshes)
	unknownA = self.obj3D.get_frame_unknown(frameIndex, 0)
	unknownB = self.obj3D.get_frame_unknown(frameIndex, 1)
        self.write_name(root)
        self.outFile.write_long(numChildren)
        
        if transform:
            self.outFile.write_float(*transform)
        else:
	    print "here"
            self.outFile.write_float(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)
	if unknownA:
	    self.outFile.write_float(*unknownA)
	else:
	    self.outFile.write_long(0, 100, 0, 0)

        self.outFile.write_long(numMesh)
	
	bbMin = self.obj3D.get_frame_bbox(frameIndex, 0)
	bbMax = self.obj3D.get_frame_bbox(frameIndex, 1)
	self.outFile.write_float(*bbMin)
        self.outFile.write_float(*bbMax)
	
	if unknownB:
	    self.outFile.write_float(*unknownB)
	else:
	    if numMesh:
		self.outFile.write_string(
		    "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00")
	    else:
		self.outFile.write_null(16)
	
	if numMesh > 0:
	    meshType = 0
	    self.outFile.write_byte(meshType)
	    for i in xrange(numMesh):
		meshName = meshes[i]
		self.write_mesh(meshName, meshType)
	    self.write_dupe_vertices(meshName)
	    self.write_bones(meshName)
							
        for i in xrange(numChildren):
            self.write_frame(subFrames[i])
            
    def build_names(self):
        
        objects = self.obj3D.get_objects()
        names = {}
        for i in xrange(len(objects)):
            meshName = objects[i]
            index = re.search("\[\d+\]", meshName)
            if index:
                meshName = meshName[:index.start()]
                if names.has_key(meshName):
                    names[meshName] += 1
                else:
                    names[meshName] = -1
            else:
                names[meshName] = 1
        return names
        
    def write_file(self):
        '''Write version 3 xx file'''
        
	#hardcode format
	self.xxFormat = 3
	#verify model compatibility
	frameCount = self.obj3D.frame_count()
	matCount = self.obj3D.mat_count()
	if not frameCount:
	    print "non-xx compatible format. Must re-build"
	    self.check_frames()
	    
	if not matCount:
	    print "no materials specified. Create one"
	    self.build_materials()
	self.write_header(3)
	print "ready"
	root = self.obj3D.get_frames()[0].get_name()
	#start writing xx at All_Root frame
	self.write_frame(root)
	self.write_materials()
	self.write_textures()
	if self.xxFormat >= 2:
	    self.outFile.write_null(10)
	    
    def build_materials(self):
	
	objects = self.obj3D.get_objects()
	for obj in objects:
	    faceCount = self.obj3D.face_count(obj)
	    for i in xrange(faceCount):
		self.obj3D.add_face_material(obj, i, 0)
		
	self.obj3D.create_material(0)
	self.obj3D.add_material_name(0, "material")
	    
    def build_frame(self, name, index, parent):
	
	self.obj3D.create_frame(index)
	self.obj3D.add_frame_name(index, name)
	
	if parent != -1:
	    self.obj3D.add_frame_child(parent, name)
	    self.obj3D.add_frame_parent(index, parent)
	    
    def check_frames(self):
	'''Verify compatibility with the xx format. Non-xx models typically
	do not use frames, so we will have to build them.'''
	
	meshes = self.obj3D.get_objects()
	
	self.build_frame("All_Root", 0, -1)
	self.build_frame("SCENE_ROOT", 1, 0)
	
	index = 2
	for i in xrange(len(meshes)):
	    self.build_frame(meshes[i], index, 1)
	    meshTransform = self.obj3D.get_mesh_transform(meshes[i])
	    
	    self.obj3D.add_frame_transform(index, meshTransform)
	    self.obj3D.add_frame_mesh(index, meshes[i])
	    index += 1
	frames = self.obj3D.get_frames()
        
def read_file(path):
    '''Read the file'''
    
    dirPath = os.path.dirname(path)
    openFile = StructReader(open(path, 'rb'))
    obj = Illusion_xx(dirPath, inFile=openFile)
    obj.parse_file()
    openFile.close()
    return obj

def write_file(path, modelObj):
    
    openFile = StructWriter(open(path + ".xx", 'wb'))
    obj = IllusionXX_Writer(obj3D=modelObj, outFile=openFile)
    obj.write_file()
    openFile.close()

def definitions():
    '''Return the header, extension, and a description of the format'''
    
    return "", "XX", "Illusion XX"